package com.sharding5.multi.mybatis.seata.config;

import com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceBuilder;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.shardingsphere.driver.api.ShardingSphereDataSourceFactory;
import org.apache.shardingsphere.infra.algorithm.core.config.AlgorithmConfiguration;
import org.apache.shardingsphere.sharding.api.config.ShardingRuleConfiguration;
import org.apache.shardingsphere.sharding.api.config.rule.ShardingTableRuleConfiguration;
import org.apache.shardingsphere.sharding.api.config.strategy.sharding.StandardShardingStrategyConfiguration;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.*;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.*;

/**
 * @author lenovo
 */
@Slf4j
@Configuration
@MapperScan(basePackages = "com.sharding5.multi.mybatis.seata.mapper.order", sqlSessionFactoryRef = "shardingSqlSessionFactory")
public class OrderDbConfig {
    @Bean(name = "saleorder01")
    @ConfigurationProperties(prefix = "spring.datasource.druid.saleorder01")
    public DataSource saleorder01() {
        log.info("初始化 saleorder01 数据源");
        return DruidDataSourceBuilder.create().build();
    }

    @Bean(name = "saleorder02")
    @ConfigurationProperties(prefix = "spring.datasource.druid.saleorder02")
    public DataSource saleorder02() {
        log.info("初始化 saleorder02 数据源");
        return DruidDataSourceBuilder.create().build();
    }

    @Primary
    @Bean("shardingDataSource")
    public DataSource shardingDataSource(@Qualifier("saleorder01") DataSource saleorder01,
                                         @Qualifier("saleorder02") DataSource saleorder02) throws SQLException {
        log.info("初始化 sharding 数据源");
        // 配置真实数据源
        Map<String, DataSource> dataSourceMap = new HashMap<>(2);
        dataSourceMap.put("saleorder01", saleorder01);
        dataSourceMap.put("saleorder02", saleorder02);

        // 订单表的分表规则配置
        ShardingTableRuleConfiguration orderTableRuleConfiguration = new ShardingTableRuleConfiguration("t_order", "saleorder01.t_order_$->{1..2},saleorder02.t_order_$->{3..4}");
        orderTableRuleConfiguration.setDatabaseShardingStrategy(new StandardShardingStrategyConfiguration("orderId", "my_database_sharding_algorithm"));
        orderTableRuleConfiguration.setTableShardingStrategy(new StandardShardingStrategyConfiguration("orderId", "my_table_sharding_algorithm_for_order"));

        ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
        // 如果有多个数据表需要分表，依次添加到这里
        shardingRuleConfig.getTables().add(orderTableRuleConfiguration);
        // shardingRuleConfig.getTables().add(this.getOrderItemTableRuleConfiguration());

        // 配置分片策略：通过代码方式
        Properties myDbShardingAlgorithmProperties = new Properties();
        myDbShardingAlgorithmProperties.setProperty("strategy", "standard");
        myDbShardingAlgorithmProperties.setProperty("algorithmClassName", "com.sharding5.multi.mybatis.seata.algorithm.MyDatabaseShardingAlgorithm");
        shardingRuleConfig.getShardingAlgorithms().put("my_database_sharding_algorithm", new AlgorithmConfiguration("CLASS_BASED", myDbShardingAlgorithmProperties));

        Properties myOrderTableShardingAlgorithmProperties = new Properties();
        myOrderTableShardingAlgorithmProperties.setProperty("strategy", "standard");
        myOrderTableShardingAlgorithmProperties.setProperty("algorithmClassName", "com.sharding5.multi.mybatis.seata.algorithm.MyOrderTableShardingAlgorithm");
        shardingRuleConfig.getShardingAlgorithms().put("my_table_sharding_algorithm_for_order", new AlgorithmConfiguration("CLASS_BASED", myOrderTableShardingAlgorithmProperties));

        // 配置分片策略：通过 SPI 方式
        // shardingRuleConfig.getShardingAlgorithms().put("my_database_sharding_algorithm", new AlgorithmConfiguration("MY_DATABASE_SHARDING_ALGORITHM", new Properties()));
        // shardingRuleConfig.getShardingAlgorithms().put("my_table_sharding_algorithm_for_order", new AlgorithmConfiguration("MY_TABLE_SHARDING_ALGORITHM_FOR_ORDER", new Properties()));

        Properties p = new Properties() {{
            this.setProperty("sql.show", Boolean.TRUE.toString());
        }};
        // 获取数据源对象
        DataSource dataSource = ShardingSphereDataSourceFactory.createDataSource(dataSourceMap, Collections.singleton(shardingRuleConfig), p);
        return dataSource;
    }

    @Bean(name = "shardingSqlSessionFactory")
    public SqlSessionFactory shardingSqlSessionFactory(@Qualifier("shardingDataSource") DataSource shardingDataSource) throws Exception {
        // 使用 MyBatis 时
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        // 使用 MyBatis-Plus 时
        // MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
        bean.setDataSource(shardingDataSource);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/order/*.xml"));
        return bean.getObject();
    }

    @Primary
    @Bean("shardingTransactionManger")
    public DataSourceTransactionManager shardingTransactionManger(@Qualifier("shardingDataSource") DataSource shardingDataSource) {
        return new DataSourceTransactionManager(shardingDataSource);
    }

    @Bean(name = "shardingSqlSessionTemplate")
    public SqlSessionTemplate shardingSqlSessionTemplate(@Qualifier("shardingSqlSessionFactory") SqlSessionFactory shardingSqlSessionFactory) {
        return new SqlSessionTemplate(shardingSqlSessionFactory);
    }
}